/*
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 */
package client;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.net.Socket;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;

import com.mysql.jdbc.StringUtils;

public class Client
{
	private String _ip = "127.0.0.1";
	private int _port = 2106;
	private Socket _socket;
	protected Shell shell;
	// private PrintStream out = null;
	private BufferedOutputStream bout = null;
	private BufferedInputStream bin = null;
	private Reader reader = null;
	private Button connectButton, disconnectButton = null;
	private static Text display = null;
	private Text input;
	private Button button;
	private Text text;
	private Text txtNickname;
	private boolean keypressed = false;

	public static void main(String[] args)
	{
		try
		{
			Client window = new Client();
			window.open();
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}

	}

	public void open()
	{
		Display display = Display.getDefault();
		createContents();
		shell.open();
		shell.layout();
		while (!shell.isDisposed())
		{
			if (!display.readAndDispatch())
			{
				display.sleep();
			}
		}
	}

	/**
	 * Create contents of the window.
	 */
	protected void createContents()
	{
		shell = new Shell(SWT.SHELL_TRIM & ~SWT.RESIZE & ~SWT.MAX);
		shell.setSize(450, 300);
		shell.setText("Client");

		connectButton = new Button(shell, SWT.NONE);
		connectButton.addSelectionListener(new SelectionAdapter()
		{
			@Override
			public void widgetSelected(SelectionEvent e)
			{
				try
				{
					_socket = new Socket(_ip, _port);
					// out = new PrintStream(_socket.getOutputStream());
					bout = new BufferedOutputStream(_socket.getOutputStream());
					text.setEditable(false);
					reader = new Reader(text.getText());
					reader.start();
					setVisibilityForAllExceptConnectButton(true);
				}
				catch (Exception ee)
				{
					if (bout != null)
						try
						{
							bout.close();
						}
						catch (IOException e1)
						{
							e1.printStackTrace();
						}
					if (ee.getMessage().contains("Connection refused: connect"))
						display.append("Server is not online.\n");
					else
						display.append(ee.getMessage() + "\n");
				}
			}
		});
		connectButton.setBounds(83, 235, 68, 23);
		connectButton.setText("Connect");

		button = new Button(shell, SWT.NONE);
		button.addSelectionListener(new SelectionAdapter()
		{
			@Override
			public void widgetSelected(SelectionEvent e)
			{
				try
				{
					String msg = input.getText();
					if (!StringUtils.isEmptyOrWhitespaceOnly(msg))
						writeData(Sifrovani.encrypt(msg));
				}
				catch (IOException e1)
				{
					e1.printStackTrace();
				}
				input.setText("");
			}
		});
		button.setText("Send");
		button.setBounds(157, 235, 68, 23);
		button.setEnabled(false);

		disconnectButton = new Button(shell, SWT.NONE);
		disconnectButton.addSelectionListener(new SelectionAdapter()
		{
			@Override
			public void widgetSelected(SelectionEvent e)
			{
				try
				{
					writeData("ClientControlShutdown");
					_socket.close();
					bout.close();
					text.setEditable(true);
					reader.interrupt();
					input.setEditable(false);
					setVisibilityForAllExceptConnectButton(false);
				}
				catch (Exception e1)
				{
				}
			}
		});
		disconnectButton.setBounds(231, 235, 68, 23);
		disconnectButton.setText("Disconnect");
		disconnectButton.setEnabled(false);

		input = new Text(shell, SWT.BORDER | SWT.WRAP | SWT.V_SCROLL);
		input.setEditable(false);
		input.setBounds(1, 107, 442, 100);

		input.addKeyListener(new KeyListener()
		{

			public void keyReleased(KeyEvent arg0)
			{
				keypressed = false;
			}

			public void keyPressed(KeyEvent arg0)
			{
				if (!keypressed)
				{
					if (arg0.keyCode == 13 || arg0.keyCode == 16777296) // Velky a maly enter
					{
						keypressed = true;
						String msg = input.getText().trim();

						System.out.println("test: " + msg);
						try
						{
							if (!StringUtils.isEmptyOrWhitespaceOnly(msg))
								writeData(Sifrovani.encrypt(msg));
						}
						catch (IOException e)
						{
							e.printStackTrace();
						}

						input.setText("");
					}
				}
			}
		});

		display = new Text(shell, SWT.WRAP | SWT.BORDER | SWT.VERTICAL);
		display.setBounds(1, 1, 442, 100);
		display.setEditable(false);

		text = new Text(shell, SWT.BORDER);
		text.setBounds(157, 213, 68, 19);

		txtNickname = new Text(shell, SWT.READ_ONLY);
		txtNickname.setText("Nickname:");
		txtNickname.setBounds(96, 213, 55, 19);
	}

	private void setVisibilityForAllExceptConnectButton(boolean b)
	{
		connectButton.setEnabled(!b);
		button.setEnabled(b);
		disconnectButton.setEnabled(false);
		input.setEditable(false);
	}

	class Reader extends Thread
	{
		// private BufferedReader in = null;
		private String Nick = "";

		public Reader(String s)
		{
			Nick = s;
		}

		public void run()
		{
			try
			{
				// in = new BufferedReader(new InputStreamReader(_socket.getInputStream()));
				bin = new BufferedInputStream(_socket.getInputStream());
			}
			catch (IOException e)
			{
				e.printStackTrace();
			}

			getPhrase();
			try
			{
				if (Nick != null && Nick.trim() != "")
					writeData("Nickname" + Nick);
				else
					writeData("NULLNickname");
			}
			catch (IOException e)
			{
				e.printStackTrace();
			}
			
			Display.getDefault().asyncExec(new Runnable()
			{
				@Override
				public void run()
				{
					disconnectButton.setEnabled(true);			
					input.setEditable(true);
				}
			});
			
			while (!isInterrupted())
			{
				try
				{
					String message = readData();

					if (message != null)
					{
						final String msg = Sifrovani.decrypt(message);
						Display.getDefault().asyncExec(new Runnable()
						{
							@Override
							public void run()
							{
								display.append(msg + "\n");
							}
						});
					}
				}
				catch (IOException e)
				{
					this.interrupt();
				}
			}
		}

		private void getPhrase()
		{
			try
			{
				Thread.sleep(100);
				// 1st pass
				String data = readData();
				Thread.sleep(100);
				Vernam v = new Vernam();
				v.setPhrase(data);
				v.generateKey();
				writeData(v.XOR());
				Thread.sleep(100);
				// 2nd pass
				String s = readData();
				Thread.sleep(100);
				v.setPhrase(s);
				Sifrovani.setPhrase(new String(v.XOR()));
				v.forget();

			}
			catch (Exception e)
			{
				e.printStackTrace();
			}
		}
	}

	private void writeData(String s) throws IOException
	{
		byte[] msg = s.getBytes();
		int len = msg.length + 2;
		bout.write(len & 0xff);
		bout.write(len >> 8 & 0xff);
		bout.write(msg);
		bout.flush();

	}

	private String readData() throws IOException
	{
		int lengthLo = bin.read();
		int lengthHi = bin.read();
		int length = lengthHi * 256 + lengthLo;

		byte[] data = new byte[length - 2];

		int receivedBytes = 0;
		int newBytes = 0;
		int left = length - 2;
		while (newBytes != -1 && receivedBytes < length - 2)
		{
			newBytes = bin.read(data, receivedBytes, left);
			receivedBytes = receivedBytes + newBytes;
			left -= newBytes;
		}

		return new String(data);
	}
}